#ifndef MYSQL_H
#define MYSQL_H

// MySql Protocol Version 10
// Specification Author: Ian Redfern <ian.redfern@logicacmg.com>
//                       OCT 05 2002
//                       http://public.logicacmg.com/~redferni/mysql/MySQL-Protocol.html
// C++ Implementation: Sekar Swaminathan
//                     sekar@sekar.net
//                     AUG 26 2003
//
// Todo: needs to be re-written using socklib::connection_buffer

#include <cstdio>
#include <ctime>
#include <exception>
#include <string>
#include <vector>

#include "socklib.h"
#include "sock.h"

#if !defined(_WIN32)
typedef long long __int64;
#define _atoi64(x) atoll(x)
#endif

struct mysql {
  enum myversion {
    version10,
  };

  enum capability {
    cap_long_password  = 0x0001,
    cap_found_rows     = 0x0002,
    cap_long_flag      = 0x0004,
    cap_connect_db     = 0x0008,
    cap_no_schema      = 0x0010,
    cap_compress       = 0x0020,
    cap_odbc           = 0x0040,
    cap_local_files    = 0x0080,
    cap_ignore_space   = 0x0100,
    cap_change_user    = 0x0200,
    cap_interactive    = 0x0400,
    cap_ssl            = 0x0800,
    cap_ignore_sigpipe = 0x1000,
    cap_transactions   = 0x2000,
    cap_reserved1      = 0x4000,
    cap_secure_auth    = 0x8000,
  };

  enum command {
    cmd_sleep          = 0,
    cmd_quit           = 1,
    cmd_init_db        = 2,
    cmd_query          = 3,
    cmd_field_list     = 4,
    cmd_create_db      = 5,
    cmd_drop_db        = 6,
    cmd_refresh        = 7,
    cmd_shutdown       = 8,
    cmd_statistics     = 9,
    cmd_process_info   = 10,
    cmd_connect        = 11,
    cmd_process_kill   = 12,
    cmd_debug          = 13,
    cmd_ping           = 14,
    cmd_time           = 15,
    cmd_delayed_insert = 16,
    cmd_change_user    = 17,
    cmd_binlog_dump    = 18,
    cmd_table_dump     = 19,
    cmd_connect_out    = 20,
  };

  enum field_type {
    ft_decimal         = 0,
    ft_tiny            = 1,
    ft_short           = 2,
    ft_long            = 3,
    ft_float           = 4,
    ft_double          = 5,
    ft_null            = 6,
    ft_timestamp       = 7,
    ft_longlong        = 8,
    ft_int24           = 9,
    ft_date            = 10,
    ft_time            = 11,
    ft_datetime        = 12,
    ft_year            = 13,
    ft_newdate         = 14,
    ft_enum            = 247,
    ft_set             = 248,
    ft_tiny_blob       = 249,
    ft_medium_blob     = 250,
    ft_long_blob       = 251,
    ft_blob            = 252,
    ft_var_string      = 253,
    ft_string          = 254,
    ft_geometry        = 255,
  };

  enum data_type {
    dt_none            = 0,
    dt_char,
    dt_short,
    dt_int,
    dt_int64,
    dt_float,
    dt_double,
    dt_time,
    dt_decimal,
    dt_string,
    dt_qstring,
    dt_chars,
    dt_voids,
    dt_bytes,
  };

  enum status_code {
    sc_in_trans        = 1,
    sc_autocommit      = 2,
  };

  enum char_set {
    cs_big5            = 1,
    cs_czech           = 2,
    cs_dec8            = 3,
    cs_dos             = 4,
    cs_german1         = 5,
    cs_hp8             = 6,
    cs_koi8_ru         = 7,
    cs_latin1          = 8,
    cs_latin2          = 9,
    cs_swe7            = 10,
    cs_usa7            = 11,
    cs_ujis            = 12,
    cs_sjis            = 13,
    cs_cp1251          = 14,
    cs_danish          = 15,
    cs_hebrew          = 16,
    cs_win1251         = 17,
    cs_tis620          = 18,
    cs_euc_kr          = 19,
    cs_estonia         = 20,
    cs_hungarian       = 21,
    cs_koi8_ukr        = 22,
    cs_win1251ukr      = 23,
    cs_gb2312          = 24,
    cs_greek           = 25,
    cs_win1250         = 26,
    cs_croat           = 27,
    cs_gbk             = 28,
    cs_cp1257          = 29,
    cs_latin5          = 30,
  };

  enum field_flag {
    ff_not_null        = 0x0001,
    ff_pri_key         = 0x0002,
    ff_unique_key      = 0x0004,
    ff_multiple_key    = 0x0008,
    ff_blob            = 0x0010,
    ff_unsigned        = 0x0020,
    ff_zerofill        = 0x0040,
    ff_binary          = 0x0080,
    ff_enum            = 0x0100,
    ff_auto_increment  = 0x0200,
    ff_timestamp       = 0x0400,
    ff_set             = 0x0800,
    ff_num             = 0x8000,
  };

  enum packet_type {
    pt_none      = 0xffffffff,
    pt_connect   = 0xfffffffe,
    pt_fields    = 0xffff00fe,
    pt_rows      = 0xffff00fd,
    pt_okay      = 0,
    pt_send_file = 0xfb,
    pt_end       = 0xfe,
    pt_error     = 0xff,
  };

  enum result_code {
    rc_fail            = 0,
    rc_success         = 1,
    rc_no_more_rows    = 2,
    rc_no_more_results = 3,
    rc_send_file       = 4,
  };

  enum verbosity {
    vdump_queries     = 0x0001,
    vdump_packets     = 0x0002,
    vdump_all        = 0xffff,
  };

  static const char* strerr(int errcode);

  ////////////////////////////datatype structures
  struct datetime: public tm {
    datetime();
    datetime(const char* dt) { set(dt); } // dt is in YYYY-mm-dd HH:MM:SS format
    datetime(time_t dt) { set(dt); }

    operator time_t() const { return get_time_t(); }
    operator std::string() const { return get_time_string(); }

    void        set(const char* dt); // dt is in YYYY-mm-dd HH:MM:SS format
    void        set(time_t dt);
    
    time_t      get_time_t() const;
    std::string get_time_string() const;// will yield in YYYY-mm-dd HH:MM:SS format
  };

  // decimal handles money and numeric as well
  struct decimal {
    socklib::bytes    value;     // value[0] is 0 for sign + and 1 for -1;
    unsigned char     length;    // total length
    unsigned char     precision; // number of places after decimal point

    decimal(int len, int prec): length(len), precision(prec) { value.insert(value.end(), length+1, 0); value[0] = 0; }

    operator double() const;
    operator std::string() const;

    void set(double d);
    void set(const char* d);
  };

  ////////////////////////////Password related hashing
  struct rand {
    unsigned long m;
    unsigned long n;

    rand(): m(0), n(0) {}

    rand operator^(const rand& b) const;

    double gen_rand();
    void   hash(const char* password);
  };

  static std::string password_digest4(const char* password, const char* salt); // pre-5 version
  static std::string password_digest5(const char* password, const char* salt); // 5 version and beyond


  /////////////////////////////Exception
  struct exception: std::exception {
    std::string errmsg;

    // $e is replaced with error string associated with error code
    exception(): errmsg("") {}
    exception(int errcode, const char* fmt, ...);
    exception(const char* fmt, ...);

    void seterrf(int errcode, const char* fmt, ...);
    void vseterrf(int errcode, const char* fmt, va_list ap);

    const char* what() const { return errmsg.c_str(); }
  };

  ////////////////////////////state structures

  struct response_info {
    command       cmdtype;
    packet_type   pkttype;
    result_code   rescode;
    
    // okay packet data
    __int64       affected_rows;
    __int64       insert_id;
    short         status;

    // tabular result
    __int64       field_count;

    // statistics
    std::string   stats;

    response_info() { clear(); }

    void clear() {
      cmdtype = cmd_sleep;
      pkttype = pt_none;
      rescode = rc_success;
      affected_rows = 0;
      insert_id     = 0;
      status        = 0;
      field_count   = 0;
      stats         = "";
    }
  };

  struct field_info {
    std::string        name;
    std::string        table;
    std::string        orig_table; // if table is an alias
    std::string        database;
    std::string        default_value;
    int                length;
    int                column_width;
    unsigned int       flags;
    int                decimals;
    field_type         fldtype;
    data_type          datatype;
    void*              data;
    int                datalen;
    bool               null_value;

    field_info()
      : name(""), table(""), orig_table(""), database(""), default_value(""),
        length(0), column_width(0), flags(0), decimals(0), fldtype(ft_geometry),
        datatype(dt_none), data(0), datalen(0), null_value(false)
    {}
  };

  struct field_infos: std::vector<field_info> {
    int current_column;

    field_infos(): current_column(0) {}
  };

private:
  SOCKET               sock;  // socket used in talking to database

  socklib::bytes       outbuf;
  int                  outpktnum;
  unsigned char        inbuf[1024]; // enough to hold two packets
  int                  inbufend;    // current inbuf end
  int                  inbufbeg;    // data is in [inbuf+inbufbeg,inbuf+inbufend)
  unsigned char        inlastpkt;
  packet_type          inpkttype;
  int                  inpktsize;
  int                  inpktnum;
  int                  inbytesleft; // bytes that are in flight to us

public:
  // login variables
  unsigned short       serverport; // in host byte order
  char                 localhost[64];
  char                 serverhost[64];
  char                 username[32];
  char                 password[32];
  std::string          salt;
  std::string          serverversion;
  int                  threadid;
  int                  capabilities;
  char_set             charset;
  myversion            version;
  int                  verbose;

  field_infos          fields;
  response_info        response;

  mysql();
  mysql(const char*            serverhostname,
        unsigned short         serverport,
        const char*            user,
        const char*            pwd,
        myversion              ver=version10,
        int                    dumpflag=0);

  ~mysql();

  void connect(const char*                 serverhostname,
	             unsigned short              server_port,
	             const char*                 user,
	             const char*                 pwd,
               myversion                   ver=version10);
  void reconnect();
  void disconnect();
  bool isconnected() const { return sock != 0; }

  void select_db(const char* dbname);

  result_code execf(const char* fmt, ...);
  result_code exec(const char* cmd);

  result_code getrow();
  result_code getresult();

  bool nextrow() { return getrow() != rc_no_more_rows; }
  void zaprows() { while(nextrow()); }
  bool nextresult() { return getresult() != rc_no_more_results; }
  void zapresults() { while(nextresult()) zaprows(); }

  // bulk copy is done as follows:
  //   bulk_insert_begin(tablename);
  //   bind(col1);
  //   bind(col2); 
  //   bind(col3); // bind all columns
  //   for each row {
  //     set col1, col2, col3 to desired values
  //     bulk_insert_row();
  //   }
  //   bulk_insert_end(); or bulk_insert_begin(tablename) or exec() will
  //   end bulk insert also.
  void bulk_insert_begin(const char* tablename);
  void bulk_insert_row();
  void bulk_insert_end();
  
  void bind(char& d);
  void bind(unsigned char& d);
  void bind(short& d);
  void bind(unsigned short& d);
  void bind(int& d);
  void bind(unsigned int& d);
  void bind(__int64& d);
  void bind(unsigned __int64& d);
  void bind(float& d);
  void bind(double& d);
  void bind(datetime& d);
  void bind(decimal& d);
  void bind(std::string& d);
  void bind(char* d, int len);
  void bind(void* d, int len);
  void bind(socklib::bytes& d);
  void bind_qstring(std::string& d);

  bool isnull(int column_index); // column_index is in [0..fields.size()-1]
  void setnull(int column_index);
  void clearnull(int column_index);

private:
  void put_command(command cmd);
  void put(unsigned char d);
  void put(short d);
  void put(unsigned short d);
  void put_int24(int d);
  void put(int d);
  void put(unsigned int d);
  void put(__int64 d);
  void put(unsigned __int64 d);
  void put(const void* d, int len);
  void put(const void* d, int len, int forcelen);
  void put_field_length(unsigned int len);
  void send_packet(int pktnum=0);

  void put_char(char d) { put((unsigned char)d); }
  void put_uchar(unsigned char d) { put(d); }
  void put_short(short d) { put(d); }
  void put_ushort(unsigned short d) { put(d); }
  void put_int(int d) { put(d); }
  void put_uint(unsigned int d) { put(d); }
  void put_int64(__int64 d) { put(d); }
  void put_uint64(unsigned __int64 d) { put(d); }
  void put_str0(const char* d) { put(d, (int)strlen(d)+1); }
  void put_str(const char* d) { put(d, (int)strlen(d)); }
  void put_string0(const std::string& d) { put(d.c_str(), (int)d.size()+1); }
  void put_string(const std::string& d) { put(d.c_str(), (int)d.size()); }

  void           unget_byte() { inbufbeg--; }
  unsigned char  get_byte();
  short          get_short();
  unsigned short get_ushort();
  int            get_int24(); // get 3 byte int
  int            get_int();
  unsigned int   get_uint();
  int            get_length_and_int();
  std::string    get_string();
  std::string    get_length_and_string();
  void           get_string(char* d, int forcelen);
  void           get(void* d, int len);
  void           get_length_and_bytes(socklib::bytes& d);
  __int64        get_field_length();
  void           trash(); // trash all remaining data from previous input packet
  void           trash(int len);
  void           recv_inbuf();

  int            inavail() const { return inbytesleft + (inbufend - inbufbeg); }

  void process_response();
  void process_fields();
  void process_rows();

  void read_field_value(field_info& field);
  void read_field_char_value(field_info& field);
  void read_field_short_value(field_info& field);
  void read_field_int_value(field_info& field);
  void read_field_int64_value(field_info& field);
  void read_field_float_value(field_info& field);
  void read_field_double_value(field_info& field);
  void read_field_time_value(field_info& field);
  void read_field_decimal_value(field_info& field);
  void read_field_string_value(field_info& field);
  void read_field_chars_value(field_info& field);
  void read_field_voids_value(field_info& field);
  void read_field_bytes_value(field_info& field);
  void read_field_qstring_value(field_info& field); // include quote char if needed
  
public:
  static const char* field_type2str(field_type fldtype);
  static const char* data_type2str(data_type datatype);
  static std::string rlike_string(const char* expr, const char* str);
  static std::string quote_string(const char* str, int quote_char='\'');
  static std::string purify_string(const char* str, int quote_char='\'');
  static std::string purify_sql_search_string(const char* str);
};

#endif // MYSQL_H